#include "Terrain.h"
#include "..\base\D3DUtil.h"
#include <fstream>
#include <cmath>
#include <cstdio>


Terrain::Terrain(IDirect3DDevice9* device, 
				 std::string heightmapFileName, 
				 int numVertsPerRow, 
				 int numVertsPerCol, 
				 int cellSpacing, 
				 float heightScale ) : 
		m_device(device),
		m_numVertsPerRow(numVertsPerRow),
		m_numVertsPerCol(numVertsPerCol),
		m_numCellsPerRow(numVertsPerRow - 1),
		m_numCellsPerCol(numVertsPerCol - 1),
		m_cellSpacing(cellSpacing),
		m_heightScale(heightScale)
{
	m_width = m_numCellsPerRow * m_cellSpacing;
	m_depth = m_numCellsPerCol * m_cellSpacing;

	m_numVertices = m_numVertsPerRow * m_numVertsPerCol;
	m_numTriangles = m_numCellsPerRow * m_numCellsPerCol * 2;

	// Load heightmap
	if (!_ReadRawFile(heightmapFileName))
	{
		::MessageBox(0, "Terrain::_ReadRawFile - FAILED", 0, 0);
		::PostQuitMessage(0);
	}

	// Scale heights
	for (int i = 0; i < m_heightmap.size(); i++)
		m_heightmap[i] *= m_heightScale;

	// Compute the vertices
	if (!_ComputeVertices())
	{
		::MessageBox(0, "Terrain::_ComputeVertices - FAILED", 0, 0);
		::PostQuitMessage(0);
	}
	// Compute the indices
	if (!_ComputeIndices())
	{
		::MessageBox(0, "Terrain::_ComputeIndices - FAILED", 0, 0);
		::PostQuitMessage(0);
	}
}


Terrain::~Terrain()
{
	d3dutil::Release<IDirect3DVertexBuffer9*>(m_vb);
	d3dutil::Release<IDirect3DIndexBuffer9*>(m_ib);
	d3dutil::Release<IDirect3DTexture9*>(m_texture);
}

int Terrain::GetHeightmapEntry( int row, int col )
{
	return m_heightmap[row * m_numVertsPerRow + col];
}

void Terrain::SetHeightmapEntry( int row, int col, int value )
{
	m_heightmap[row * m_numVertsPerRow + col] = value;
}

float Terrain::GetHeightVerbose( float x, float z )
{
	// Translate on xz-plane by the transformation that
	// takes the Terrain START point to the origin.
	x = ((float)m_width / 2.0f) + x;
	z = ((float)m_depth / 2.0f) + z;

	// Scale down by the transformation that makes the
	// cellspacing equal to one. This is given by 
	// 1 / cellSpacing since; cellspcing * 1 / cellspaceing = 1;
	x /= (float)m_cellSpacing;
	z /= (float)m_cellSpacing;

	// From now on, we will interpret our positive z-axis as 
	// going in the 'down' direction, rather than the 'up' direction.
	// This allows to extract the row and column simply by 'flooring'
	// x and z:
	float col = ::floorf(x);
	float row = ::floorf(z);

	printf("(col, row) (%.2f, %.2f)\n", col, row);
	// get the heights of the quad we're in
	// A   B
	// *---*
	// | / |
	// *---*
	// C   D
	float A = GetHeightmapEntry(row, col);
	float B = GetHeightmapEntry(row, col+1);
	float C = GetHeightmapEntry(row+1, col);
	float D = GetHeightmapEntry(row+1, col+1);
	printf("(A, B, C, D):(%.2f, %.2f, %.2f, %.2f)\n", A, B, C, D);
	// Find the triangle we are in
	float dx = x - col;
	float dz = z - row;

	float height = 0.0f;
	if (dz < 1.0 - dx) // upper triangle ABC
	{
		float uy = B - A; // A->B
		float vy = C - A; // A->C

		// Linearly interpolate on each vector. The height is the vectex
		// height the vectors u and v from {A}, plus the heights
		// found by interpolating on each vector u and v.
		height = A + d3dutil::Lerp(0.0f, uy, dx) + d3dutil::Lerp(0.0f, vy, dz);
	}
	else // lower triangle DCB
	{
		float uy = C - D; // D->C
		float vy = B - D; // D->B

		// Linearly interpolate on each vector. The height is the vectex
		// height the vectors u and v originate from {D}, plus the heights 
		// found by the interpolating on each vector u and v.
		height = D + d3dutil::Lerp(0.0f, uy, 1.0f - dx) + d3dutil::Lerp(0.0f, vy, 1.0-dz);
	}
	if (height < 0.0f)
	{
		height = 0.0f;
	}
	return height;
}
float Terrain::GetHeight( float x, float z )
{
	// Translate on xz-plane by the transformation that
	// takes the Terrain START point to the origin.
	x = ((float)m_width / 2.0f) + x;
	z = ((float)m_depth / 2.0f) + z;

	// Scale down by the transformation that makes the
	// cellspacing equal to one. This is given by 
	// 1 / cellSpacing since; cellspcing * 1 / cellspaceing = 1;
	x /= (float)m_cellSpacing;
	z /= (float)m_cellSpacing;

	// From now on, we will interpret our positive z-axis as 
	// going in the 'down' direction, rather than the 'up' direction.
	// This allows to extract the row and column simply by 'flooring'
	// x and z:
	float col = ::floorf(x);
	float row = ::floorf(z);

	// get the heights of the quad we're in
	// A   B
	// *---*
	// | / |
	// *---*
	// C   D
	float A = GetHeightmapEntry(row, col);
	float B = GetHeightmapEntry(row, col+1);
	float C = GetHeightmapEntry(row+1, col);
	float D = GetHeightmapEntry(row+1, col+1);

	// Find the triangle we are in
	float dx = x - col;
	float dz = z - row;

	float height = 0.0f;
	if (dz < 1.0 - dx) // upper triangle ABC
	{
		float uy = B - A; // A->B
		float vy = C - A; // A->C

		// Linearly interpolate on each vector. The height is the vectex
		// height the vectors u and v from {A}, plus the heights
		// found by interpolating on each vector u and v.
		height = A + d3dutil::Lerp(0.0f, uy, dx) + d3dutil::Lerp(0.0f, vy, dz);
	}
	else // lower triangle DCB
	{
		float uy = C - D; // D->C
		float vy = B - D; // D->B

		// Linearly interpolate on each vector. The height is the vectex
		// height the vectors u and v originate from {D}, plus the heights 
		// found by the interpolating on each vector u and v.
		height = D + d3dutil::Lerp(0.0f, uy, 1.0f - dx) + d3dutil::Lerp(0.0f, vy, 1.0-dz);
	}
	if (height < 0.0f)
	{
		height = 0.0f;
	}
	return height;
}
bool Terrain::LoadTexture( std::string fileName )
{
	HRESULT hr = 0;

	hr = D3DXCreateTextureFromFile(
		m_device,
		fileName.c_str(),
		&m_texture);

	if (FAILED(hr))
		return false;

	return true;
}

bool Terrain::GenerateTexture( D3DXVECTOR3* directionToLight )
{
	// Methods fill the top surface of a texture procedurally.
	// Then lights the top surface. Finally, it fills the other
	// mipmap surefaces based on the top surface data using
	// D3DXFilterTexture.
	HRESULT hr = 0;

	// texel for each quad cell.
	int texWidth  = m_numCellsPerRow;
	int texHeight = m_numCellsPerCol;

	// Create an empty texture.
	hr = D3DXCreateTexture(
		m_device,
		texWidth, texHeight,
		0, // create a complete mimmap train
		0, // usage
		D3DFMT_X8B8G8R8, // 32 bit XRGB format
		D3DPOOL_MANAGED, &m_texture);

	if (FAILED(hr))
		return false;

	D3DSURFACE_DESC textureDesc;
	m_texture->GetLevelDesc(0 /*level*/, &textureDesc);

	// Make sure we got the requested format because our code
	// that fills the texture is hard coded to a 32 pixel depth.
	if(textureDesc.Format != D3DFMT_X8R8G8B8)
		return false;

	D3DLOCKED_RECT lockedRect;
	m_texture->LockRect(0 /*lock top surface*/, &lockedRect,
		0 /* lock entries tex*/, 0 /*flags*/);

	DWORD* imageData = (DWORD*)lockedRect.pBits;
	for(int i = 0; i < texHeight; i++)
	{
		for (int j = 0; j < texWidth; j++)
		{
			D3DXCOLOR c;

			// get height of upper left vertex of quad.
			float height = (float)GetHeightmapEntry(i, j) / m_heightScale;

			if ((height) < 42.5f)	 c = d3dutil::BEACH_SAND;
			else if(height < 85.0f)  c = d3dutil::LIGHT_YELLOW_GREEN;
			else if(height < 127.5f) c = d3dutil::PUREGREEN;
			else if(height < 170.5f) c = d3dutil::DARK_YELLOW_GREEN;
			else if(height < 212.5f) c = d3dutil::DARKBROWN;
			else                     c = d3dutil::WHITE;

			// fill locked data, note we divide the pitch by four because
			// the pitch is given in bytes and there are 4 bytes per DWORD
			imageData[i * lockedRect.Pitch / 4 + j] = (D3DCOLOR)c;
		}
	}
	m_texture->UnlockRect(0);
	
	if (!_LightTerrain(directionToLight))
	{
		::MessageBox(0, " Terrain::_LightTerrain - FAILED", 0, 0);
		return false;
	}
	
	hr = D3DXFilterTexture(
		m_texture,
		0, // default palette
		0, // use top level as source level
		D3DX_DEFAULT); // default filter

	if (FAILED(hr))
	{
		::MessageBox(0, " D3DXFilterTexture - FAILED", 0, 0);
		return false;
	}

	return true;
}

bool Terrain::Draw( D3DXMATRIX *world, bool drawTris )
{
	HRESULT hr = 0;

	if (m_device)
	{
		m_device->SetTransform(D3DTS_WORLD, world);

		m_device->SetStreamSource(0, m_vb, 0, sizeof(TerrainVertex));
		m_device->SetFVF(TerrainVertex::FVF);
		m_device->SetIndices(m_ib);
		
		m_device->SetTexture(0, m_texture);

		// turn off lighting since we're lighting it ourselves
		m_device->SetRenderState(D3DRS_LIGHTING, false);

		hr = m_device->DrawIndexedPrimitive(
			D3DPT_TRIANGLELIST,
			0, 0, 
			m_numVertices,
			0,
			m_numTriangles);

		m_device->SetRenderState(D3DRS_LIGHTING, true);
		
		if (drawTris)
		{
			m_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
			hr = m_device->DrawIndexedPrimitive(
				D3DPT_TRIANGLELIST,
				0, 0, 
				m_numVertices,
				0,
				m_numTriangles);
			m_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
		}

		if(FAILED(hr))
			return false;
	}
	return true;
}

bool Terrain::_ReadRawFile( std::string fileName )
{
	// Restriction: RAW file dimensions must be >= to the
	// dimensions of the terrain. That is a 128*128 RAW file
	// can only be used with a terrain constructed with at most
	// 128x128 vertices

	// A height for each each vertex.
	std::vector<BYTE> in(m_numVertices);

	std::ifstream inFile(fileName.c_str(), std::ios_base::binary);

	if (inFile == 0)
		return false;

	inFile.read(
		(char*)&in[0], // buffer
		in.size()); // number of bytes to read into buffer

	inFile.close();

	// copy BYTE vector to int vector
	m_heightmap.resize(m_numVertices);

	for(int i = 0; i < in.size(); i++)
		m_heightmap[i] = in[i];

	return true;
}

bool Terrain::_ComputeVertices()
{
	HRESULT hr = 0;

	hr = m_device->CreateVertexBuffer(
		m_numVertices * sizeof(TerrainVertex),
		D3DUSAGE_WRITEONLY,
		TerrainVertex::FVF,
		D3DPOOL_MANAGED,
		&m_vb,
		0);
	if (FAILED(hr))
		return false;
	
	// coordinates to start generating vertices at
	int startX = -m_width / 2;
	int startZ = m_depth / 2;

	// coordinates to end generating vertices at
	int endX = m_width / 2;
	int endZ = -m_depth / 2;

	// compute the increment size of the texture coordinates
	// from one vertex to the next
	float uCoordIncreSize = 1.0f / (float)m_numCellsPerRow;
	float vCoordIncreSize = 1.0f / (float)m_numCellsPerCol;

	TerrainVertex *v = 0;
	m_vb->Lock(0, 0, (void**)&v, 0);
	int i = 0; 
	for (int z = startZ; z >= endZ; z -= m_cellSpacing)
	{
		int j = 0;
		for(int x = startX; x <= endX; x += m_cellSpacing)
		{
			// compute the correct index into vertex buffer and 
			// height map based on we are in nested loop.
			int index = i * m_numVertsPerRow + j;
			v[index] = TerrainVertex(
				(float)x, 
				(float)m_heightmap[index],
				(float)z, 
				(float)j * uCoordIncreSize,
				(float)i * vCoordIncreSize);
			j++;
		}
		i++;
	}	

	m_vb->Unlock();
	return true;
}

bool Terrain::_ComputeIndices()
{
	HRESULT hr = 0;
	hr = m_device->CreateIndexBuffer(
		m_numTriangles * 3 * sizeof(WORD), // 3 indices per triangle
		D3DUSAGE_WRITEONLY,
		D3DFMT_INDEX16,
		D3DPOOL_MANAGED,
		&m_ib,
		0);

	if (FAILED(hr))
		return false;

	WORD* indices = 0;
	m_ib->Lock(0, 0, (void**)&indices, 0);

	// index to start of a group of 6 indices that describe the 
	// two triangles that make up a quad.
	int baseIndex = 0;
	
	// loop through and compute the triangles of each quad.
	for (int i = 0; i < m_numCellsPerCol; i++)
	{
		for(int j = 0; j < m_numCellsPerRow; j++)
		{
			indices[baseIndex] = i * m_numVertsPerRow + j;
			indices[baseIndex+1] = i * m_numVertsPerRow + j + 1;
			indices[baseIndex+2] = (i+1) * m_numVertsPerRow + j;

			indices[baseIndex+3] = (i+1) * m_numVertsPerRow + j;
			indices[baseIndex+4] = i * m_numVertsPerRow + j + 1;
			indices[baseIndex+5] = (i+1) * m_numVertsPerRow + j + 1;

			baseIndex += 6;
		}
	}

	m_ib->Unlock();
	return true;
}

bool Terrain::_LightTerrain( D3DXVECTOR3* directionToLight )
{
	HRESULT hr = 0;

	D3DSURFACE_DESC textureDesc;
	m_texture->GetLevelDesc(0 /*level*/, &textureDesc);

	if(textureDesc.Format != D3DFMT_X8R8G8B8)
		return false;

	D3DLOCKED_RECT lockedRect;
	m_texture->LockRect(
		0,
		&lockedRect,
		0,
		0);

	DWORD* imageData = (DWORD*)lockedRect.pBits;
	for (int i = 0; i < textureDesc.Height-1; i++)
	{
		for(int j = 0; j < textureDesc.Width; j++)
		{
			int index = i * lockedRect.Pitch / 4 + j;

			// get current color of quad
			D3DXCOLOR c(imageData[index]);
			c *= _ComputeShade(i, j, directionToLight);

			// save shaded color
			imageData[index] = (D3DCOLOR)c;
		}
	}
	m_texture->UnlockRect(0);
	return true;
}

float Terrain::_ComputeShade( int cellRow, int cellCol, D3DXVECTOR3* directionToLight )
{
	// get heights of three vertices on the quad
	float heightA = GetHeightmapEntry(cellRow, cellCol);
	float heightB = GetHeightmapEntry(cellRow, cellCol+1);
	float heightC = GetHeightmapEntry(cellRow+1, cellCol);

	// build two vectors on the quad.
	D3DXVECTOR3  u(m_cellSpacing, heightB - heightA, 0.0f);
	D3DXVECTOR3  v(0.0f,          heightC - heightA, -m_cellSpacing);

	// find the normal by taking the cross product of two
	// vector on the quad
	D3DXVECTOR3 n;
	D3DXVec3Cross(&n ,&u, &v);
	D3DXVec3Normalize(&n, &n);

	float cosine = D3DXVec3Dot(&n, directionToLight);

	if (cosine < 0.0f)
		cosine  = 0.0f;

	return cosine;
}
